﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Threading;
using Black.SurfaceToolkit.Tools;
using Microsoft.Surface.Presentation.Controls;

namespace Black.SurfaceToolkit.Controls
{
    /// <summary>
    /// Représente un conteneur d'un <see cref="T:Black.SurfaceToolkit.Controls.CoverFlow" />.
    /// </summary>
    [Browsable(true), Description("Conteneur d'un CoverFlow")]
    public class CoverFlowItem : SurfaceListBoxItem
    {
        #region Propriétés de dépendance

        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.IsHosted" />.
        /// </summary>
        private static readonly DependencyPropertyKey IsHostedPropertyKey = DependencyProperty.RegisterReadOnly("IsHosted", typeof(bool), typeof(CoverFlowItem), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.Distance" />.
        /// </summary>
        public static readonly DependencyProperty DistanceProperty = StringPathPanel.DistanceProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.FollowingMargin" />.
        /// </summary>
        public static readonly DependencyProperty FollowingMarginProperty = StringPathPanel.FollowingMarginProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.FreeSpaceMode" />.
        /// </summary>
        public static readonly DependencyProperty FreeSpaceModeProperty = StringPathPanel.FreeSpaceModeProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.HorizontalPathAlignment" />.
        /// </summary>
        public static readonly DependencyProperty HorizontalPathAlignmentProperty = StringPathPanel.HorizontalPathAlignmentProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.IsAfterLine" />.
        /// </summary>
        public static readonly DependencyProperty IsAfterLineProperty = StringPathPanel.IsAfterLineProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.IsBeforeLine" />.
        /// </summary>
        public static readonly DependencyProperty IsBeforeLineProperty = StringPathPanel.IsBeforeLineProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.IsDistanceLocked" />.
        /// </summary>
        public static readonly DependencyProperty IsDistanceLockedProperty = StringPathPanel.IsDistanceLockedProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.IsFreeSpaceNeeded" />.
        /// </summary>
        public static readonly DependencyProperty IsFreeSpaceNeededProperty = StringPathPanel.IsFreeSpaceNeededProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.Offset" />.
        /// </summary>
        public static readonly DependencyProperty OffsetProperty = StringPathPanel.OffsetProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.Orientation" />.
        /// </summary>
        public static readonly DependencyProperty OrientationProperty = StringPathPanel.OrientationProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.Position" />.
        /// </summary>
        public static readonly DependencyProperty PositionProperty = StringPathPanel.PositionProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.PrecedingFreeSpace" />.
        /// </summary>
        public static readonly DependencyProperty PrecedingFreeSpaceProperty = StringPathPanel.PrecedingFreeSpaceProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.PrecedingMargin" />.
        /// </summary>
        public static readonly DependencyProperty PrecedingMarginProperty = StringPathPanel.PrecedingMarginProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.RelativeIndex" />.
        /// </summary>
        public static readonly DependencyProperty RelativeIndexProperty = CoverFlow.RelativeIndexProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.Size" />.
        /// </summary>
        public static readonly DependencyProperty SizeProperty = StringPathPanel.SizeProperty.AddOwner(typeof(CoverFlowItem));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.VerticalPathAlignment" />.
        /// </summary>
        public static readonly DependencyProperty VerticalPathAlignmentProperty = StringPathPanel.VerticalPathAlignmentProperty.AddOwner(typeof(CoverFlowItem));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItem.IsHosted" />.
        /// </summary>
        public static readonly DependencyProperty IsHostedProperty = CoverFlowItem.IsHostedPropertyKey.DependencyProperty;

        #endregion

        #region Accesseurs

        /// <summary>
        /// Obtient ou définit la distance à l'origine.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Distance à l'origine"), Category("CoverFlowItem")]
        public double Distance
        {
            get
            {
                return (double)this.GetValue(CoverFlowItem.DistanceProperty);
            }
            set
            {
                this.SetValue(CoverFlowItem.DistanceProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit la marge basse de l'élément.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Marge basse de l'élément"), Category("CoverFlowItem")]
        public double FollowingMargin
        {
            get
            {
                return (double)this.GetValue(CoverFlowItem.FollowingMarginProperty);
            }
            set
            {
                this.SetValue(CoverFlowItem.FollowingMarginProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le mode de fonctionnement de l'espace libre.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Mode de fonctionnement de l'espace libre"), Category("CoverFlowItem")]
        public FreeSpaceMode FreeSpaceMode
        {
            get
            {
                return (FreeSpaceMode)this.GetValue(CoverFlowItem.FreeSpaceModeProperty);
            }
            set
            {
                this.SetValue(CoverFlowItem.FreeSpaceModeProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit l'alignement horizontal par rapport au chemin.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Alignement horizontal par rapport au chemin"), Category("CoverFlowItem")]
        public HorizontalAlignment HorizontalPathAlignment
        {
            get
            {
                return (HorizontalAlignment)this.GetValue(CoverFlowItem.HorizontalPathAlignmentProperty);
            }
            set
            {
                this.SetValue(CoverFlowItem.HorizontalPathAlignmentProperty, value);
            }
        }

        /// <summary>
        /// Obtient le conteneur.
        /// </summary>
        [Browsable(false)]
        public ItemsControl Host
        {
            get
            {
                return (ItemsControl.ItemsControlFromItemContainer(this));
            }
        }

        /// <summary>
        /// Obtient la position du conteneur.
        /// </summary>
        [Browsable(false)]
        public int Index
        {
            get
            {
                ItemsControl oHost = this.Host;

                if (oHost == null)
                    return (-1);

                int iIndex = oHost.Items.IndexOf(this);

                if (iIndex == -1)
                    iIndex = oHost.ItemContainerGenerator.IndexFromContainer(this);

                return (iIndex);
            }
        }

        /// <summary>
        /// Obtient une valeur indiquant si l'élément spécifié est après la fin de la ligne.
        /// </summary>
        [Browsable(false)]
        public bool IsAfterLine
        {
            get
            {
                return (bool)this.GetValue(CoverFlowItem.IsAfterLineProperty);
            }
        }

        /// <summary>
        /// Obtient une valeur indiquant si l'élément spécifié est avant le début de la ligne.
        /// </summary>
        [Browsable(false)]
        public bool IsBeforeLine
        {
            get
            {
                return (bool)this.GetValue(CoverFlowItem.IsBeforeLineProperty);
            }
        }

        /// <summary>
        /// Obtient ou définit une valeur indiquant si la distance de l'élément est vérrouillée.
        /// </summary>
        [Browsable(false)]
        public bool IsDistanceLocked
        {
            get
            {
                return (bool)this.GetValue(CoverFlowItem.IsDistanceLockedProperty);
            }
        }

        /// <summary>
        /// Obtient ou définit valeur indiquant si de l'espace libre est requis.
        /// </summary>
        [Browsable(false)]
        public bool IsFreeSpaceNeeded
        {
            get
            {
                return (bool)this.GetValue(CoverFlowItem.IsFreeSpaceNeededProperty);
            }
            set
            {
                this.SetValue(CoverFlowItem.IsFreeSpaceNeededProperty, value);
            }
        }

        /// <summary>
        /// Obtient une valeur indiquant si l'élément est géré par un <see cref="T:System.Windows.Controls.ItemsControl" />.
        /// </summary>
        [Browsable(false)]
        public bool IsHosted
        {
            get
            {
                return (bool)this.GetValue(CoverFlowItem.IsHostedProperty);
            }
            private set
            {
                this.SetValue(CoverFlowItem.IsHostedPropertyKey, BooleanBoxes.Box(value));
            }
        }

        /// <summary>
        /// Obtient le décalage de l'élément.
        /// </summary>
        [Browsable(false)]
        public double Offset
        {
            get
            {
                return (double)this.GetValue(CoverFlowItem.OffsetProperty);
            }
        }

        /// <summary>
        /// Obtient l'orientation (en degrés) de l'élément.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Orientation (en degrés) de l'élément"), Category("CoverFlowItem")]
        public double Orientation
        {
            get
            {
                return (double)this.GetValue(CoverFlowItem.OrientationProperty);
            }
        }

        /// <summary>
        /// Obtient la distance en nombre d'éléments.
        /// </summary>
        [Browsable(false)]
        public int Position
        {
            get
            {
                return (int)this.GetValue(CoverFlowItem.PositionProperty);
            }
        }
        
        /// <summary>
        /// Obtient ou définit l'espace libre avant l'élément.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Espace libre avant l'élément"), Category("CoverFlowItem")]
        public double PrecedingFreeSpace
        {
            get
            {
                return (double)this.GetValue(CoverFlowItem.PrecedingFreeSpaceProperty);
            }
            set
            {
                this.SetValue(CoverFlowItem.PrecedingFreeSpaceProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit la marge haute de l'élément.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Marge haute de l'élément"), Category("CoverFlowItem")]
        public double PrecedingMargin
        {
            get
            {
                return (double)this.GetValue(CoverFlowItem.PrecedingMarginProperty);
            }
            set
            {
                this.SetValue(CoverFlowItem.PrecedingMarginProperty, value);
            }
        }

        /// <summary>
        /// Obtient l'indice relativement à l'élément séléctionné.
        /// </summary>
        [Browsable(false)]
        public int RelativeIndex
        {
            get
            {
                return (int)this.GetValue(CoverFlowItem.RelativeIndexProperty);
            }
        }

        /// <summary>
        /// Obtient la taille de l'élément.
        /// </summary>
        [Browsable(false)]
        public double Size
        {
            get
            {
                return (double)this.GetValue(CoverFlowItem.SizeProperty);
            }
        }

        /// <summary>
        /// Obtient la source de donnée attachée.
        /// </summary>
        [Browsable(false)]
        public IList Source
        {
            get
            {
                ItemsControl oHost = this.Host;

                if (oHost == null)
                    return (null);

                return (oHost.ItemsSource as IList ?? oHost.Items);
            }
        }

        /// <summary>
        /// Obtient ou définit l'alignement vertical par rapport au chemin.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Alignement vertical par rapport au chemin"), Category("CoverFlowItem")]
        public VerticalAlignment VerticalPathAlignment
        {
            get
            {
                return (VerticalAlignment)this.GetValue(CoverFlowItem.VerticalPathAlignmentProperty);
            }
            set
            {
                this.SetValue(CoverFlowItem.VerticalPathAlignmentProperty, value);
            }
        }

        #endregion

        #region Constructeurs

        /// <summary>
        /// Initialise la classe <see cref="T:Black.SurfaceToolkit.Controls.CoverFlowItem" />.
        /// </summary>
        static CoverFlowItem()
        {
            FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(CoverFlowItem), new FrameworkPropertyMetadata(typeof(CoverFlowItem)));

            CommandManager.RegisterClassCommandBinding(typeof(CoverFlowItem), new CommandBinding(CoverFlowItemCommands.Remove, new ExecutedRoutedEventHandler(CoverFlowItem.OnRemove), new CanExecuteRoutedEventHandler(CoverFlowItem.OnQueryRemove)));
            CommandManager.RegisterClassCommandBinding(typeof(CoverFlowItem), new CommandBinding(CoverFlowItemCommands.MoveTo, new ExecutedRoutedEventHandler(CoverFlowItem.OnMoveTo), new CanExecuteRoutedEventHandler(CoverFlowItem.OnQueryMoveTo)));
            CommandManager.RegisterClassCommandBinding(typeof(CoverFlowItem), new CommandBinding(CoverFlowItemCommands.MoveOf, new ExecutedRoutedEventHandler(CoverFlowItem.OnMoveOf), new CanExecuteRoutedEventHandler(CoverFlowItem.OnQueryMoveOf)));
            CommandManager.RegisterClassCommandBinding(typeof(CoverFlowItem), new CommandBinding(CoverFlowItemCommands.Select, new ExecutedRoutedEventHandler(CoverFlowItem.OnSelect), new CanExecuteRoutedEventHandler(CoverFlowItem.OnQuerySelect)));
        }

        /// <summary>
        /// Initialise une nouvelle instance de <see cref="T:Black.SurfaceToolkit.Controls.CoverFlowItem" />.
        /// </summary>
        public CoverFlowItem()
            : base()
        {
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Déplace l'élément du nombre de positons spécifié.
        /// </summary>
        /// <param name="p_iOffset">Nombre de position.</param>
        /// <param name="p_bKeepPositions">Valeur indiquant s'il faut conserver les positions des éléments.</param>
        /// <param name="p_bKeepSelectedIndex">Valeur indiquant s'il faut conserver l'index de selection.</param>
        /// <returns><see langword="true" /> si l'élément a été déplacé, <see langword="false" /> sinon.</returns>
        public bool MoveOf(int p_iOffset, bool p_bKeepPositions, bool p_bKeepSelectedIndex)
        {
            int iIndex = Index;
            if (iIndex == -1)
                return (false);

            return (MoveTo(iIndex + p_iOffset, p_bKeepPositions, p_bKeepSelectedIndex));
        }

        /// <summary>
        /// Déplace l'élément à la positon spécifiée.
        /// </summary>
        /// <param name="p_iPosition">Position</param>
        /// <param name="p_bKeepPositions">Valeur indiquant s'il faut conserver les positions des éléments.</param>
        /// <param name="p_bKeepSelectedIndex">Valeur indiquant s'il faut conserver l'index de selection.</param>
        /// <returns><see langword="true" /> si l'élément a été déplacé, <see langword="false" /> sinon.</returns>
        public virtual bool MoveTo(int p_iPosition, bool p_bKeepPositions, bool p_bKeepSelectedIndex)
        {
            IList oSource = this.Source;

            if (oSource == null)
                return (false);

            ItemsControl oHost = Host;
            int iIndex = this.Index;

            if (oSource.IsReadOnly)
                return (false);

            if (p_iPosition < 0)
                return (false);

            if (p_iPosition >= oSource.Count)
                return (false);

            if (p_iPosition == iIndex)
                return (true);

            if (iIndex > p_iPosition)
            {
                // Les ExitActions et EnterActions ne sont pas prisent en compte, on diffère
                for (int i = iIndex - 1; i >= p_iPosition; i--)
                {
                    this.Dispatcher.BeginInvoke(new Action<int>(itemIndex =>
                    {
                        CoverFlowItem oItemContainer = CoverFlowItem.ContainerAt<CoverFlowItem>(itemIndex, oHost);
                        oItemContainer.MoveTo(itemIndex + 1, p_bKeepPositions, p_bKeepSelectedIndex);
                    }), DispatcherPriority.Loaded, i);
                }

                return (true);
            }

            bool bWasSelected = this.IsSelected;
            int iSelectedIndex = -1;
            Selector oSelector = oHost as Selector;

            if (oSelector != null && p_bKeepSelectedIndex)
            {
                iSelectedIndex = oSelector.SelectedIndex;
            }

            double dPreviousDistance = this.Distance;
            double dThisSize = this.Size;
            double dHoverSize = iIndex > p_iPosition ? CoverFlowItem.SizeOfRange(p_iPosition, iIndex - 1, oHost)
                                                  : CoverFlowItem.SizeOfRange(iIndex + 1, p_iPosition, oHost);

            // Déplacement
            object oCookie = this.PrepareForCopy();
            object oPreviousData = oSource[iIndex];
            oSource.RemoveAt(iIndex);
            oSource.Insert(p_iPosition, oPreviousData);

            UIElement oNewThisContainer = CoverFlowItem.ContainerAt<UIElement>(p_iPosition, oHost);
            RestoreCopy(oNewThisContainer, oCookie);
            ((FrameworkElement)oNewThisContainer).ApplyTemplate();

            oHost.UpdateLayout();

            double dDeltaNextDistance = StringPathPanel.GetFollowingMargin(oNewThisContainer)
                                      - this.FollowingMargin;
            double dDeltaPreviousDistance = (StringPathPanel.GetPrecedingMargin(oNewThisContainer) + StringPathPanel.GetPrecedingFreeSpace(oNewThisContainer))
                                          - (this.PrecedingMargin + this.PrecedingFreeSpace);

            if (p_bKeepPositions)
            {
                if (iIndex > p_iPosition)
                {
                    CoverFlowItem.AskForFreeSpace(oNewThisContainer, dHoverSize - dDeltaPreviousDistance, true);

                    UIElement oFollowingThisContainer = CoverFlowItem.ContainerAt<UIElement>(p_iPosition + 1, oHost);

                    if (oFollowingThisContainer != null)
                        CoverFlowItem.AskForFreeSpace(oFollowingThisContainer, -dHoverSize - dThisSize + dDeltaPreviousDistance + dDeltaNextDistance, true);

                    UIElement oContainerPostIndex = CoverFlowItem.ContainerAt<UIElement>(iIndex + 1, oHost);

                    if (oContainerPostIndex != null)
                        CoverFlowItem.AskForFreeSpace(oContainerPostIndex, dThisSize - dDeltaPreviousDistance - dDeltaNextDistance, true);
                }
                else
                {
                    UIElement oContainerIndex = CoverFlowItem.ContainerAt<UIElement>(iIndex, oHost);

                    if (oContainerIndex != null)
                        CoverFlowItem.AskForFreeSpace(oContainerIndex, dThisSize, true);

                    CoverFlowItem.AskForFreeSpace(oNewThisContainer, -(dHoverSize + dThisSize + dDeltaNextDistance), true);

                    UIElement oFollowingThisContainer = CoverFlowItem.ContainerAt<UIElement>(p_iPosition + 1, oHost);

                    if (oFollowingThisContainer != null)
                        CoverFlowItem.AskForFreeSpace(oFollowingThisContainer, dHoverSize - dDeltaNextDistance, true);
                }
            }

            oSelector.SelectedIndex = -1;

            StringPathPanel.SetDistance(oNewThisContainer, dPreviousDistance);

            oHost.UpdateLayout();

            // Mise à jour de la séléction

            if (bWasSelected && !p_bKeepSelectedIndex)
            {
                Selector.SetIsSelected(oNewThisContainer, true);
            }

            if (oSelector != null && p_bKeepSelectedIndex)
            {
                if (iSelectedIndex == -1)
                    iSelectedIndex = 0;

                UIElement oSelectedContainer = CoverFlowItem.ContainerAt<UIElement>(iSelectedIndex, oHost);

                if (oSelectedContainer != null)
                    Selector.SetIsSelected(oSelectedContainer, true);
            }

            oHost.UpdateLayout();
            CommandManager.InvalidateRequerySuggested();

            return (true);
        }

        /// <summary>
        /// Supprime, si possible, l'élément de son conteneur.
        /// </summary>
        /// <param name="p_bKeepPositions">Valeur indiquant s'il faut conserver les positions des éléments.</param>
        /// <returns><see langword="true" /> si l'élément a été supprimé, <see langword="false" /> sinon.</returns>
        public virtual bool Remove(bool p_bKeepPositions)
        {
            IList oSource = this.Source;

            if (oSource == null)
                return (false);

            ItemsControl oHost = this.Host;
            int iIndex = this.Index;

            if (oSource.IsReadOnly)
                return (false);

            bool bWasSelected = this.IsSelected;

            if (p_bKeepPositions && iIndex > 0)
            {
                int iNextItemIndex = iIndex + 1;

                if (iNextItemIndex < oHost.Items.Count)
                {
                    UIElement oNextUIElement = CoverFlowItem.ContainerAt<UIElement>(iNextItemIndex, oHost);

                    if (oNextUIElement != null)
                    {
                        CoverFlowItem.AskForFreeSpace(oNextUIElement, FollowingMargin + PrecedingMargin + PrecedingFreeSpace, true);
                    }
                }
            }

            oSource.RemoveAt(iIndex);

            if (oHost.HasItems && bWasSelected)
            {
                int iNewSelectedIndex = Math.Max(0, iIndex - 1);
                UIElement oNewSelectedContainer = CoverFlowItem.ContainerAt<UIElement>(iNewSelectedIndex, oHost);

                Selector.SetIsSelected(oNewSelectedContainer, true);
            }

            return (true);
        }

        #endregion

        #region Fonctions protégées

        /// <summary>
        /// Prépare l'élément pour une copie imminente.
        /// </summary>
        /// <returns>Information envoyée à la copie.</returns>
        protected virtual object PrepareForCopy()
        {
            return (null);
        }

        /// <summary>
        /// Finalise la copie de l'élément spécifié.
        /// </summary>
        /// <param name="p_oCopy">Copie de cette instance.</param>
        /// <param name="p_oCookie">Information issue de la préparation à la copie.</param>
        protected virtual void RestoreCopy(UIElement p_oCopy, object p_oCookie)
        {
            StringPathPanel.SetPrecedingFreeSpace(p_oCopy, this.PrecedingFreeSpace);
        }

        #endregion

        #region Fonctions statiques protégées

        /// <summary>
        /// Ajoute de l'espace libre à l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément sur lequel rajouter <paramref name="p_dFreeSpace" /> d'espace libre.</param>
        /// <param name="p_dFreeSpace">Quantité d'espace libre à ajouter sur <paramref name="p_oElement" />.</param>
        /// <param name="p_bCumulate">Valeur indiquant s'il faut cummuler l'espace libre.</param>
        protected static void AskForFreeSpace(UIElement p_oElement, double p_dFreeSpace, bool p_bCumulate)
        {
            double dPrecedingFreeSpace = StringPathPanel.GetPrecedingFreeSpace(p_oElement);

            if (p_bCumulate)
                p_dFreeSpace += dPrecedingFreeSpace;

            if (dPrecedingFreeSpace != p_dFreeSpace)
            {
                bool bOldIsFreeSpaceNeeded = StringPathPanel.GetIsFreeSpaceNeeded(p_oElement);

                StringPathPanel.SetIsFreeSpaceNeeded(p_oElement, true);

                StringPathPanel.SetPrecedingFreeSpace(p_oElement, p_dFreeSpace);

                StringPathPanel.SetIsFreeSpaceNeeded(p_oElement, bOldIsFreeSpaceNeeded);
            }
        }

        /// <summary>
        /// Obtient le conteneur de l'index spécifié.
        /// </summary>
        /// <typeparam name="T">Type du conteneur.</typeparam>
        /// <param name="p_iIndex">Index dont on veut le conteneur.</param>
        /// <param name="p_oHost">Parent du conteneur.</param>
        /// <returns>Conteneur de position <paramref name="p_iIndex" />.</returns>
        protected static T ContainerAt<T>(int p_iIndex, ItemsControl p_oHost)
            where T : class
        {
            if (p_oHost == null)
                return (null);

            if (p_iIndex >= p_oHost.Items.Count || p_iIndex < 0)
                return (null);

            DependencyObject oContainer = p_oHost.ItemContainerGenerator.ContainerFromIndex(p_iIndex);

            if (oContainer == null)
                oContainer = (DependencyObject)p_oHost.Items[p_iIndex];

            return (oContainer as T);
        }

        #endregion

        #region Fonctions statiques privées

        /// <summary>
        /// Détermine si la commande <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItemCommands.MoveOf" /> peut être exécutée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnQueryMoveOf(object p_oSender, CanExecuteRoutedEventArgs p_oArgs)
        {
            CoverFlowItem oCoverFlowItem = p_oSender as CoverFlowItem;

            if (oCoverFlowItem != null)
            {
                IConvertible oParameter = p_oArgs.Parameter as IConvertible;

                if (oParameter != null)
                {
                    int iIndex = oParameter.ToInt32(null) + oCoverFlowItem.Index;

                    IList oSource = oCoverFlowItem.Source;
                    p_oArgs.CanExecute = oSource != null
                                      && !oSource.IsReadOnly
                                      && iIndex >= 0
                                      && iIndex < oSource.Count;
                    p_oArgs.Handled = true;
                }
            }
        }

        /// <summary>
        /// Détermine si la commande <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItemCommands.MoveTo" /> peut être exécutée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnQueryMoveTo(object p_oSender, CanExecuteRoutedEventArgs p_oArgs)
        {
            CoverFlowItem oCoverFlowItem = p_oSender as CoverFlowItem;

            if (oCoverFlowItem != null)
            {
                IConvertible oParameter = p_oArgs.Parameter as IConvertible;

                if (oParameter != null)
                {
                    int iIndex = oParameter.ToInt32(null);

                    IList oSource = oCoverFlowItem.Source;
                    p_oArgs.CanExecute = oSource != null
                                      && !oSource.IsReadOnly
                                      && iIndex >= 0
                                      && iIndex < oSource.Count;
                    p_oArgs.Handled = true;
                }
            }
        }

        /// <summary>
        /// Détermine si la commande <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItemCommands.Remove" /> peut être exécutée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnQueryRemove(object p_oSender, CanExecuteRoutedEventArgs p_oArgs)
        {
            CoverFlowItem oCoverFlowItem = p_oSender as CoverFlowItem;

            if (oCoverFlowItem != null)
            {
                IList oSource = oCoverFlowItem.Source;
                p_oArgs.CanExecute = oSource != null && !oSource.IsReadOnly;
                p_oArgs.Handled = true;
            }
        }

        /// <summary>
        /// Détermine si la commande <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItemCommands.Select" /> peut être exécutée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnQuerySelect(object p_oSender, CanExecuteRoutedEventArgs p_oArgs)
        {
            CoverFlowItem oCoverFlowItem = p_oSender as CoverFlowItem;

            if (oCoverFlowItem != null)
            {
                p_oArgs.CanExecute = !oCoverFlowItem.IsSelected;
                p_oArgs.Handled = true;
            }
        }

        /// <summary>
        /// Appelée lorsque la commande <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItemCommands.MoveOf" /> est soumise.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnMoveOf(object p_oSender, ExecutedRoutedEventArgs p_oArgs)
        {
            CoverFlowItem oCoverFlowItem = p_oSender as CoverFlowItem;

            if (oCoverFlowItem != null)
            {
                IConvertible oParameter = p_oArgs.Parameter as IConvertible;

                if (oParameter != null)
                {
                    p_oArgs.Handled = oCoverFlowItem.MoveOf(oParameter.ToInt32(null), true, true);
                }
            }
        }

        /// <summary>
        /// Appelée lorsque la commande <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItemCommands.MoveTo" /> est soumise.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnMoveTo(object p_oSender, ExecutedRoutedEventArgs p_oArgs)
        {
            CoverFlowItem oCoverFlowItem = p_oSender as CoverFlowItem;

            if (oCoverFlowItem != null)
            {
                IConvertible oParameter = p_oArgs.Parameter as IConvertible;

                if (oParameter != null)
                {
                    p_oArgs.Handled = oCoverFlowItem.MoveTo(oParameter.ToInt32(null), true, true);
                }
            }
        }

        /// <summary>
        /// Appelée lorsque la commande <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItemCommands.Remove" /> est soumise.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnRemove(object p_oSender, ExecutedRoutedEventArgs p_oArgs)
        {
            CoverFlowItem oCoverFlowItem = p_oSender as CoverFlowItem;

            if (oCoverFlowItem != null)
            {
                if (p_oArgs.Parameter == null)
                {
                    p_oArgs.Handled = oCoverFlowItem.Remove(true);
                }
                else
                {
                    p_oArgs.Handled = oCoverFlowItem.Remove((bool)p_oArgs.Parameter);
                }
            }
        }

        /// <summary>
        /// Appelée lorsque la commande <see cref="P:Black.SurfaceToolkit.Controls.CoverFlowItemCommands.Select" /> est soumise.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnSelect(object p_oSender, ExecutedRoutedEventArgs p_oArgs)
        {
            CoverFlowItem oCoverFlowItem = p_oSender as CoverFlowItem;

            if (oCoverFlowItem != null)
            {
                p_oArgs.Handled = oCoverFlowItem.IsSelected = true;
            }
        }

        /// <summary>
        /// Obtient la taille de la zone spécifiée.
        /// </summary>
        /// <param name="p_iFirstIndex">Index du début de zone.</param>
        /// <param name="p_iLastIndex">Index de fin de zone.</param>
        /// <param name="p_oHost">Conteneur.</param>
        /// <returns>Taille de la zone.</returns>
        private static double SizeOfRange(int p_iFirstIndex, int p_iLastIndex, ItemsControl p_oHost)
        {
            double dResult = 0.0;

            for (int i = p_iFirstIndex; i <= p_iLastIndex; i++)
            {
                UIElement oItemContainer = CoverFlowItem.ContainerAt<UIElement>(i, p_oHost);

                dResult += StringPathPanel.GetSize(oItemContainer);
            }

            return dResult;
        }

        #endregion
    }
}
